"""
@File    : cv2_backend.py.py
@Time    : 2025/7/15 下午10:49
@Author  : xiashuobad
@Desc    : 

/**
 * _ooOoo_
 * o8888888o
 * 88" . "88
 * (| -_- |)
 *  O\ = /O
 * ___/`---'\____
 * .   ' \\| |// `.
 * / \\||| : |||// \
 * / _||||| -:- |||||- \
 * | | \\\ - /// | |
 * | \_| ''\---/'' | |
 * \ .-\__ `-` ___/-. /
 * ___`. .' /--.--\ `. . __
 * ."" '< `.___\_<|>_/___.' >'"".
 * | | : `- \`.;`\ _ /`;.`/ - ` : | |
 * \ \ `-. \_ __\ /__ _/ .-` / /
 * ======`-.____`-.___\_____/___.-`____.-'======
 * `=---=' bug泛滥 佛已瘫痪
"""
# rsio/core/backends/cv2_backend.py
import logging
import os
import numpy as np
from pathlib import Path
from typing import Literal, Sequence
from .base_backend import ImageBackend
from ..utils.exceptions import *
from ..utils import (catch_exception, require_attr_not_empty, require_python_library, type_check, ArrayShapeConverter)

logger = logging.getLogger(__name__)


class OpencvImage(ImageBackend, backend_name='cv2'):
    open_mode = Literal['r', 'w']

    def __init__(self, file_path: str, mode: open_mode = 'r', **kwargs):
        super().__init__(file_path, mode, **kwargs)

    def _checked_mode(self, mode: open_mode) -> open_mode:
        if mode not in ['r', 'w']:
            raise ImageModeError(f'不支持的打开模式：{mode}，请传入 "r" 或 "w"')
        return mode

    @require_python_library('cv2', 'opencv-python')
    def _open(self):
        import cv2
        if self.mode == 'r':
            flags = self.kwargs.get('flags', 1)
            with catch_exception(ImageOpenError()):
                self._img_array = cv2.imdecode(np.fromfile(self.file_path, dtype=np.uint8), flags)

    @ImageBackend.img_array.setter
    def img_array(self, array: np.ndarray):
        with catch_exception(ImageProcessError()):
            self._img_array = ArrayShapeConverter.array_shape_to_cv2(array)

    def set_img_array(self, array: np.ndarray) -> 'OpencvImage':
        """
        设置图像数组
        :param array: numpy.ndarray
        :return: None
        """
        self.img_array = array
        return self

    @require_attr_not_empty('img_array')
    @ImageBackend.require_write_mode
    def save(self, save_path: str = None, **meta):
        with catch_exception(ImageWriteError("cv2写入图像失败！")):
            import cv2
            save_path = save_path or self.file_path
            os.makedirs(os.path.dirname(save_path), exist_ok=True)
            ext = Path(save_path).suffix.lower()
            self.kwargs.update(meta)
            success, encoded_image = cv2.imencode(ext, self.img_array, **self.kwargs)
            encoded_image.tofile(save_path)

    @property
    @require_attr_not_empty('img_array')
    def shape(self) -> tuple[int, ...]:
        return self.img_array.shape

    @property
    @require_attr_not_empty('img_array')
    def size(self) -> tuple[int, ...]:
        return self.img_array.shape[1::-1]

    @property
    @require_attr_not_empty('img_array')
    def channel(self) -> int:
        if len(self.shape) == 2:
            return 1
        return self.shape[-1]

    @property
    @require_attr_not_empty('img_array')
    def width(self) -> int:
        return self.size[0]

    @property
    @require_attr_not_empty('img_array')
    def height(self) -> int:
        return self.size[1]

    @property
    @require_attr_not_empty('img_array')
    def dtype(self) -> np.dtype:
        return self.img_array.dtype

    @require_attr_not_empty('img_array')
    def resize(self,
               target_size: tuple[int, int] | int,
               keep_ratio: bool = False,
               pad: int = None,
               resample: int = None,
               box: list[int, int, int, int] | tuple[int, int, int, int] = None,
               **kwargs
               ) -> 'OpencvImage':
        self._img_array = self.resize_img(self.img_array, target_size, keep_ratio, pad, resample=resample, box=box,
                                          **kwargs)
        return self

    @require_attr_not_empty('img_array')
    def reverse_rgb(self) -> 'OpencvImage':
        with catch_exception(ImageProcessError()):
            if self.channel == 3:
                self.img_array = self.img_array[..., ::-1]
            elif self.channel > 3:
                self.img_array[..., :3] = self.img_array[..., 2::-1]
            else:
                logger.warning(f'[yellow]当前array的通道数为 {self.channels}，无需进行RGB反转！', extra={'markup': True})
            return self

    @require_attr_not_empty('img_array')
    @type_check
    def read_window_array(
            self,
            x_off: int = 0,
            y_off: int = 0,
            window_x_size: int = None,
            window_y_size: int = None,
            band_indexes: int | list[int] = None,
    ) -> np.ndarray:
        with catch_exception(ImageReadError()):
            window_x_size = window_x_size or self.width
            window_y_size = window_y_size or self.height
            window_array = self.img_array[y_off:y_off + window_y_size, x_off:x_off + window_x_size]
            if band_indexes and len(window_array.shape) == 3:
                window_array = window_array[..., band_indexes]
            if len(array_shape := window_array.shape) == 3 and array_shape[2] == 1:
                window_array = window_array[..., 0]
            return window_array

    @require_attr_not_empty('img_array')
    def show(self, window_name: str = None):
        self.show_image(self.img_array, window_name or self.file_name)

    @require_attr_not_empty('img_array')
    @type_check
    def crop(self,
             x_off: int = 0,
             y_off: int = 0,
             window_x_size: int = None,
             window_y_size: int = None
             ) -> 'OpencvImage':
        """
        裁剪图片
        :param x_off: 裁剪的起始x坐标
        :param y_off: 裁剪的起始y坐标
        :param window_x_size: 裁剪的宽度
        :param window_y_size: 裁剪的高度
        :return: 裁剪后的图片对象
        """
        x_off, y_off, window_x_size, window_y_size = self._checked_crop_window(x_off, y_off, window_x_size,
                                                                               window_y_size)
        self.img_array = self.img_array[y_off:y_off + window_y_size, x_off:x_off + window_x_size]
        return self

    def _checked_crop_window(
            self,
            x_off: int,
            y_off: int,
            window_x_size: int,
            window_y_size: int
    ) -> tuple[int, int, int, int]:
        if not window_x_size and not window_y_size:
            raise ValueError("裁剪的宽度和高度不能同时为空或0！")
        if not window_x_size:
            window_x_size = window_y_size
        if not window_y_size:
            window_y_size = window_x_size
        if x_off < 0:
            x_off = 0
        if y_off < 0:
            y_off = 0
        if x_off + window_x_size > self.width:
            window_x_size = self.width - x_off
        if y_off + window_y_size > self.height:
            window_y_size = self.height - y_off

        return x_off, y_off, window_x_size, window_y_size

    @require_attr_not_empty('img_array')
    def convert_to_rgb(self):
        self.img_array = ArrayShapeConverter.array_shape_to_rgb(self.img_array)

    @staticmethod
    @require_python_library('cv2', 'opencv-python')
    @type_check
    def write_array(file_path: str, array: np.ndarray, params: Sequence[int] = None):
        import cv2
        with catch_exception(ImageWriteError('cv2写入图片出错！')):
            array = ArrayShapeConverter.array_shape_to_cv2(array)
            ext = Path(file_path).suffix.lower()
            success, encoded_image = cv2.imencode(ext, array, params)
            encoded_image.tofile(file_path)

    @staticmethod
    @require_python_library('cv2', 'opencv-python')
    def show_image(img_array: np.ndarray, window_name: str = 'image'):
        import cv2
        with catch_exception(ImageShowError()):
            img_array = ArrayShapeConverter.array_shape_to_cv2(img_array)
            cv2.imshow(window_name, img_array)
            cv2.waitKey(0)

    @staticmethod
    @require_python_library('cv2', 'opencv-python')
    @type_check
    def resize_img(
            img_array: np.ndarray,
            target_size: tuple[int, int] | int,
            keep_ratio: bool = False,
            pad: int = None,
            out: np.ndarray = None,
            fx: float = None,
            fy: float = None,
            resample: int = None,
            box: list[int, int, int, int] | tuple[int, int, int, int] = None,
            shape_style: str = 'cv2'
    ) -> np.ndarray:
        import cv2
        with catch_exception(ImageProcessError('图像resize操作失败！')):
            img_array = ArrayShapeConverter.array_shape_to_cv2(img_array)
            if box is not None:
                img_array = img_array[box[1]:box[3], box[0]:box[2]]
            if isinstance(target_size, int):
                target_size = (target_size, target_size)
            if not keep_ratio:
                return cv2.resize(img_array, target_size, out, fx, fy, resample)
            # 计算缩放比例
            src_height, src_width = img_array.shape[:2]
            dst_width, dst_height = target_size
            scale = min(dst_width / src_width, dst_height / src_height)
            # 计算新的尺寸
            new_width = int(src_width * scale)
            new_height = int(src_height * scale)
            resized_img = cv2.resize(img_array, (new_width, new_height), out, fx, fy, resample)
            # 如果不填充，直接返回缩放后的图像
            if pad is None:
                return resized_img
            # 创建填充图像
            result = np.full((dst_height, dst_width, img_array.shape[2]), pad, dtype=img_array.dtype)
            # 计算粘贴位置
            paste_x = (dst_width - new_width) // 2
            paste_y = (dst_height - new_height) // 2
            # 将缩放后的图像粘贴到中心位置
            result[paste_y:paste_y + new_height, paste_x:paste_x + new_width] = resized_img
            return ArrayShapeConverter.convert(shape_style, result)

    @staticmethod
    @type_check
    def reversed_rgb(img_array: np.ndarray, shape_style: str = 'cv2') -> np.ndarray:
        img_array = ArrayShapeConverter.array_shape_to_cv2(img_array.copy())
        img_shape = img_array.shape
        match len(img_shape):
            case 2:
                logger.warning('[yellow]img_array的维度为2，无需进行RGB反转！', extra={'markup': True})
            case 3:
                img_array[..., :3] = img_array[..., 2::-1]

        return ArrayShapeConverter.convert(shape_style, img_array)

    @require_attr_not_empty('img_array')
    def __getattr__(self, attr: str):
        return getattr(self.img_array, attr)
